package com.huahai.security.core.authentication.filter;

import com.huahai.security.core.exception.TokenException;
import com.huahai.security.core.properties.SecurityProperties;
import com.huahai.security.core.properties.StatusCodeEnum;
import com.huahai.security.core.authentication.handler.CustomAuthenticationFailureHandler;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author: Jun
 * @Date: 2020/7/18
 *
 * JWT 授权认证过滤器 继承于BasicAuthenticationFilter用于权限验证
 * 每一次请求都需要检查该用户是否有该权限去操作该资源
 *
 * 在这里主要就是检查该用户是否登录了以及登录信息是否过期
 */
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {

    private final Logger logger = LoggerFactory.getLogger(getClass());
    private SecurityProperties securityProperties;
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    public JwtAuthorizationFilter(AuthenticationManager authenticationManager,SecurityProperties securityProperties,
                                  CustomAuthenticationFailureHandler customAuthenticationFailureHandler) {
        super(authenticationManager);
        this.securityProperties = securityProperties;
        this.customAuthenticationFailureHandler = customAuthenticationFailureHandler;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {

        UsernamePasswordAuthenticationToken authentication = getAuthentication(request,response);

        if (authentication == null) {
            chain.doFilter(request, response);
            return;
        }
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request,HttpServletResponse response) {
        String token = request.getHeader(securityProperties.getJwt().getJwtTokenHeader());

        if (!StringUtils.isEmpty(token) && token.startsWith(securityProperties.getJwt().getJwtTokenPrefix())) {
            try {
                Jws<Claims> parsedToken = Jwts.parserBuilder()
                        .setSigningKey(securityProperties.getJwt().getJwtSecret().getBytes())
                        .build()
                        .parseClaimsJws(token.replace(securityProperties.getJwt().getJwtTokenPrefix(), ""));

                String username = parsedToken.getBody().getSubject();

                List<SimpleGrantedAuthority> roleList = ((List<?>) parsedToken.getBody()
                        .get("rol")).stream()
                        .map(authority -> new SimpleGrantedAuthority((String) authority))
                        .collect(Collectors.toList());

                if (!StringUtils.isEmpty(username)) {
                    return new UsernamePasswordAuthenticationToken(username, null, roleList);
                }
            } catch (ExpiredJwtException exception) {
                logger.warn("Request to parse expired JWT : {} failed : {}", token, exception.getMessage());
                customAuthenticationFailureHandler.doTokenException(request,response,
                        new TokenException(StatusCodeEnum.HSC500001.getCode(),StatusCodeEnum.HSC500001.getMessage()));
            } catch (UnsupportedJwtException exception) {
                logger.warn("Request to parse unsupported JWT : {} failed : {}", token, exception.getMessage());
                customAuthenticationFailureHandler.doTokenException(request,response,
                        new TokenException(StatusCodeEnum.HSC500002.getCode(),StatusCodeEnum.HSC500002.getMessage()));
            } catch (MalformedJwtException exception) {
                logger.warn("Request to parse invalid JWT : {} failed : {}", token, exception.getMessage());
                customAuthenticationFailureHandler.doTokenException(request,response,
                        new TokenException(StatusCodeEnum.HSC500003.getCode(),StatusCodeEnum.HSC500003.getMessage()));
            } catch (SignatureException exception) {
                logger.warn("Request to parse with invalid signature : {} failed : {}", token, exception.getMessage());
                customAuthenticationFailureHandler.doTokenException(request,response,
                        new TokenException(StatusCodeEnum.HSC500004.getCode(),StatusCodeEnum.HSC500004.getMessage()));
            } catch (IllegalArgumentException exception) {
                logger.warn("Request to parse empty or null JWT : {} failed : {}", token, exception.getMessage());
                customAuthenticationFailureHandler.doTokenException(request,response,
                        new TokenException(StatusCodeEnum.HSC500005.getCode(),StatusCodeEnum.HSC500005.getMessage()));
            }
        }
        return null;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

    public void setCustomAuthenticationFailureHandler(CustomAuthenticationFailureHandler customAuthenticationFailureHandler) {
        this.customAuthenticationFailureHandler = customAuthenticationFailureHandler;
    }
}
